//include standard headers
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <time.h>
#include <string>
#include "GLFW\glfw3.h"
#include "glm\glm.hpp"
using namespace glm;
#include "rapidxml.hpp"
using namespace rapidxml;

#include "Shape.h"
#include "Plane.h"
#include "Tesseract.h"
#include "Scene.h"
#include "Renderer.h"
#include "ImageBuffer.h"
#include "DiffuseShader.h"
#include "ReflectiveShader.h"
#include "SpecularShader.h"
#include "XMLFileLoader.h"

#include "Stats.h"

#define IMG_WIDTH 512
#define IMG_HEIGHT 512
#define IMG_DEPTH 512

float raster[IMG_WIDTH * IMG_HEIGHT * 3];

void draw();
void copyImgToRaster(ImageBuffer *img);
void setPixel(int x, int y, vec4 _col);
static void error_callback(int error, const char* description){
	fprintf(stderr, description);
}
bool mousePressed = false;
bool redraw = true;

//for testing the vector, matrix, and stack classes
int main(void){
	GLFWwindow* window;
	glfwSetErrorCallback(error_callback);

	//initialize GLFW
	if (!glfwInit()){
		fprintf(stderr, "Failed to initialize GLFW\n");
		return -1;
	}
	//initialize the raster array
	for (int i = 0; i < IMG_WIDTH * IMG_HEIGHT * 3; i++){
		raster[i] = ((float)i) / (IMG_WIDTH * IMG_HEIGHT * 3);
	}

	//create the window
	window = glfwCreateWindow(IMG_WIDTH, IMG_HEIGHT, "Lab1", NULL, NULL);
		//the old way: glfwOpenWindow(512, 512, 0, 0, 0, 0, 32, 0, GLFW_WINDOW)
	//open a window and create its OpenGL context
	if (!window){
		fprintf(stderr, "Failed to open GLFW window.");
		glfwTerminate();
		return -1;
	}

	glfwMakeContextCurrent(window);

	//This might be the way to allow dynamically-sized arrays
	//int twenty = 29;
	//float *raster = new float[twenty];

	//ensure we can capture the escape key being pressed below
	//glfwEnable(GLFW_STICKY_KEYS);


	//create a scene, renderer, and imagebuffer
	//Scene* theScene = new Scene();
	
	/*
	//Camera* mainCam = new Camera(vec3(0, 0, 0), IMG_WIDTH, IMG_HEIGHT, 45.0f);
	Camera* mainCam = new Camera(vec3(0, 0, 0), IMG_WIDTH, IMG_HEIGHT, 30.0f);
	mainCam->lookAt(vec3(0, 5.0f, 15.0f), vec3(0, 2, 0), vec3(0, 1, 0));
	//mainCam->lookAt(vec3(0, 5.0f, 15.0f), vec3(0, 0, 0), vec3(0, 1, 0));
	theScene->addCamera(*mainCam);
	*/
	
	SceneManager* sceneMan = new SceneManager();
	//sceneMan->scene(theScene);
	XMLFileLoader sceneLoader = XMLFileLoader();
	sceneLoader.setScene(sceneMan);
	sceneLoader.loadFileToScene("4d_box.xml");

	//return 0;

	/*
	SurfaceShader *specular = new SpecularShader(1.0f, vec4(1, 1, 1, 1), 8);

	ShaderCollection *blueDiffuse = new ShaderCollection();
	blueDiffuse->scene(theScene);
	SurfaceShader *diff_blue = new DiffuseShader(1.0f, vec4(0, 0, 0.7, 1));
	blueDiffuse->addShader(diff_blue);
	theScene->addShader(*blueDiffuse);

	ShaderCollection *redDiffuse = new ShaderCollection();
	redDiffuse->scene(theScene);
	SurfaceShader *diff_red = new DiffuseShader(0.8f, vec4(0.7, 0, 0, 1));
	redDiffuse->addShader(diff_red);
	redDiffuse->addShader(specular);
	theScene->addShader(*redDiffuse);

	ShaderCollection *orangeDiffuse = new ShaderCollection();
	orangeDiffuse->scene(theScene);
	SurfaceShader *diff_orange = new DiffuseShader(0.8f, vec4(1, 0.4f, 0.09f, 1));
	SurfaceShader *specular_light = new SpecularShader(0.7f, vec4(1, 1, 1, 1), 8);
	orangeDiffuse->addShader(diff_orange);
	orangeDiffuse->addShader(specular_light);
	theScene->addShader(*orangeDiffuse);

	ShaderCollection *greenDiffuse = new ShaderCollection();
	greenDiffuse->scene(theScene);
	
	greenDiffuse->addShader(specular);
	SurfaceShader *diff_green = new DiffuseShader(1.0f, vec4(0, 0.8, 0, 1));
	greenDiffuse->addShader(diff_green);
	theScene->addShader(*greenDiffuse);

	ShaderCollection *chromeShader = new ShaderCollection();
	chromeShader->scene(theScene);
	SurfaceShader *reflective = new ReflectiveShader(0.8f, vec4(0, 0, 0, 0));
	chromeShader->addShader(specular);
	chromeShader->addShader(reflective);
	chromeShader->addShader(diff_red);
	theScene->addShader(*chromeShader);

	vec4 planeUp = vec4(0, 1, 0, 0);
	Shape *thePlane = new Plane(vec4(0, -2.0f, 0, 0), planeUp);
	thePlane->shader(blueDiffuse);
	Shape *theSphere = new Sphere(vec4(3.0f, -1.0f, 0.0f, 0), 1.0f);
	Shape *theSphere2 = new Sphere(vec4(-3.0f, 0, -2.0f, 0), 2.0f);
	Shape *theSphere3 = new Sphere(vec4(0, -1.0f, -3.5f, 0), 1.0f);
	theSphere->shader(redDiffuse);
	theSphere2->shader(chromeShader);
	theSphere3->shader(greenDiffuse);
	
	theScene->addShape(thePlane);
	theScene->addShape(theSphere);
	theScene->addShape(theSphere2);
	theScene->addShape(theSphere3);

	//make a triangle
	vec3 _pt1 = vec3(0, 0, 7);
	vec3 _pt2 = vec3(4, 0, 0);
	vec3 _pt3 = vec3(0, 0, -3.0f);
	vec3 _pt4 = vec3(-4.0f, -1, 5);

	//these need to be CCW
	vec3 pt1 = vec3(-4, 0, 0);
	vec3 pt2 = vec3(0, 0, 4);
	vec3 pt3 = vec3(4, 0, 0);
	Shape *theTri_new = new Triangle(vec4(0, 0, 0, 0), pt1, pt2, pt3);
	theTri_new->shader(greenDiffuse);
	//theScene->addShape(theTri_new);
	Shape *theSphere_new = new Sphere(vec4(0, 0, 0, 0), 3.98f);
	//theScene->addShape(theSphere_new);


	Shape *theTri = new Triangle(vec4(0, 0, 0, 0), _pt1, _pt2, _pt3);
	Shape *theTri2 = new Triangle(vec4(0, 0, 0, 0), _pt4, _pt1, _pt3);
	theTri->shader(greenDiffuse);
	theTri2->shader(greenDiffuse);
	//theScene->addShape(theTri);
	//theScene->addShape(theTri2);
	

	OBJLoader theLoader = OBJLoader(theScene);
	theLoader.setShader(orangeDiffuse);
	//theLoader.loadToScene("player_tri_large_pos.obj");
	//theLoader.loadToScene("suzanne.obj");
	//theLoader.loadToScene("ajax_pos.obj");

	//return 0;

	Light *theLight = new Light(vec3(0, 1, 1), vec4(1, 1, 1, 1), 0.75f);
	theScene->addLight(theLight);

	Light *theLight2 = new Light(vec3(-5, 5, 0), vec4(1, 1, 1, 1), 2.0f);
	theScene->addLight(theLight2);

	ImageBuffer* img = new ImageBuffer(IMG_WIDTH, IMG_HEIGHT, 2);	//create an image with 2 layers
	
	
	Renderer render = Renderer();
	render.camera(mainCam);
	render.image(img);
	render.scene(theScene);
	render.maxBounces(2);
	render.AAsamples(2);		//set the AA samples to 2x2
	render.AAmaxSamples(5);
	//render.AAmode(2);			//turn on dynamic samples anti-aliasing
	//render.AAmode(1);			//turn on uniform samples anti-aliasing

	Stats* theStats = Stats::instance();

	clock_t t;
	t = clock();
	if (render.prepare()){
		render.render();
	}
	t = clock() - t;
	printf("Render took %d clicks (%f seconds).\n", t, ((float)t)/CLOCKS_PER_SEC);
	theStats->toString();
	*/

	//convert it to greyscale
	//img->toGrey(1, 0, 1);
	//img->edgeDetect(0, 1);
	//img->toGrey(1, 1, 1);
	//set the active layer to the greyscale one
	//img->activeLayer(1);

	//testing ImageBuffer
	//fill the image buffer
	/*for (int y = 0; y < IMG_HEIGHT; y++){
		float perc = (float)y / (float)IMG_HEIGHT;
		vec3 theCol = vec3(perc, perc, perc);
		vec3 bgCol = vec3(0.6, 0.2, 0.2);
		for (int x = 0; x < IMG_WIDTH; x++){
			if (glm::distance(vec2(x, y), vec2(IMG_WIDTH / 2, IMG_HEIGHT / 2)) < 200)
				img->pixel(x, y, theCol);
			else
				img->pixel(x, y, bgCol);
		}
	}*/
	
	Renderer *render = sceneMan->getRender();
	ImageBuffer *img = sceneMan->getImg();
	copyImgToRaster(img);

	do{
		if (glfwGetKey(window, GLFW_KEY_KP_1) == GLFW_PRESS) {
			img->activeLayer(0);
			copyImgToRaster(img);
			redraw = true;
		}
		else if (glfwGetKey(window, GLFW_KEY_KP_2) == GLFW_PRESS) {
			if (img->layers() > 1){
				img->activeLayer(1);
				copyImgToRaster(img);
				redraw = true;
			}
		}
		else if (glfwGetKey(window, GLFW_KEY_KP_3) == GLFW_PRESS) {
			if (img->layers() > 2){
				img->activeLayer(2);
				copyImgToRaster(img);
				redraw = true;
			}
		}
		if (!mousePressed && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS){
			mousePressed = true;
			double thex;
			double they;
			glfwGetCursorPos(window, &thex, &they);
			they = (double)IMG_HEIGHT - they;
			render->debugPixel((int)thex, (int)they, 0);	//TODO: Which z value do we give here?
		}
		else if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_RELEASE){
			mousePressed = false;
		}

		if (redraw){
			draw();

			//swap buffers
			glfwSwapBuffers(window);
		}
		
		glfwPollEvents();
	}//check if the ESC key was pressed or the window was closed
	while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && true /*glfwGetWindowParam(GLFW_OPENED)*/);

	glfwDestroyWindow(window);
	//close OpenGL window and terminate GLFW
	glfwTerminate();

	return 0;
}

void draw(){
	redraw = false;
	//red background
	glClearColor(0.6, 0, 0, 0);
	//clear the screen to the clear color
	glClear(GL_COLOR_BUFFER_BIT);

	//Draw the array of pixels (This is where you draw the values
	//you have stored in the array 'raster')
	glRasterPos2f(-1, -1);
	glDrawPixels(IMG_WIDTH, IMG_HEIGHT, GL_RGB, GL_FLOAT, raster);

	if (false){
		//save the old state so that you can set it back after you draw
		GLint oldmatrixmode;
		GLboolean depthWasEnabled = glIsEnabled(GL_DEPTH_TEST);
		glDisable(GL_DEPTH_TEST);
		glGetIntegerv(GL_MATRIX_MODE, &oldmatrixmode);
		glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity();
		glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity();

		

		//set the state back to what it was
		glPopMatrix();
		glMatrixMode(GL_MODELVIEW); glPopMatrix();
		glMatrixMode(oldmatrixmode);
		if (depthWasEnabled){
			glEnable(GL_DEPTH_TEST);
		}
	}
	glFlush();
}

void setPixel(int x, int y, vec4 _col){
	raster[((y * IMG_WIDTH) + x) * 3 + 0] = _col.r;
	raster[((y * IMG_WIDTH) + x) * 3 + 1] = _col.g;
	raster[((y * IMG_WIDTH) + x) * 3 + 2] = _col.b;
}

void copyImgToRaster(ImageBuffer *img){
	//put the image contents into the raster array
	for (int y = 0; y < IMG_HEIGHT; y++){
		//vector<vec4> theRow = img->rowAt(y);
		for (int x = 0; x < IMG_WIDTH; x++){
			//TODO: adjust the z value based on which layer the user is currently viewing
			setPixel(x, y, img->pixel(x, y, 0));
		}
	}
}